﻿using System.Collections.Generic;
using Spring.Data.Generic;
using Spring.Data.Common;
using System.Data;
using System.Text;
using System;
using System.Reflection;

namespace Domain.Repository.Impl
{
    /// <summary>
    /// Use the QueryCacheService
    /// </summary>
    public class QueryTemplateImpl : AdoDaoSupport, IQueryTemplate
    {
        protected IQueryCacheService queryCacheService;

        public IQueryCacheService QueryCacheService
        {
            set
            {
                this.queryCacheService = value;
            }
        }

        public IList<IDictionary<string, object>> QueryForListMap(string sqlkey, QueryParameters queryParameters)
        {
            string querySql = GetStatementSql(sqlkey);
            WriteSqlToLog(querySql);
            IDbParameters parameters = BuildDbParameters(queryParameters);

            IList<IDictionary<string, object>> queryResult = null;
            //Do'nt use cache
            if (null == queryCacheService)
            {
                queryResult = QueryForListMap(querySql, parameters);
                return queryResult;
            }

            //use cache
            if (queryCacheService.Contains(sqlkey, queryParameters))
            {
                queryResult = queryCacheService.Get<IList<IDictionary<string, object>>>(sqlkey, queryParameters);
            }
            else
            {
                queryResult = QueryForListMap(querySql, parameters);

                queryCacheService.Add<IList<IDictionary<string, object>>>(sqlkey, queryParameters, queryResult);
            }
            return queryResult;
        }

        protected IList<IDictionary<string, object>> QueryForListMap(string querySql, IDbParameters parameters)
        {
            return AdoTemplate.QueryWithRowMapperDelegate<IDictionary<string, object>>(CommandType.Text, querySql, delegate(IDataReader reader, int num)
            {
                IDictionary<string, object> rowdata = new Dictionary<string, object>();
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    rowdata.Add(reader.GetName(i), reader.GetValue(i));
                }

                return rowdata;
            }, parameters);
        }

        public T QueryForObject<T>(string sqlkey, QueryParameters queryParameters) where T : class
        {
            string querySql = GetStatementSql(sqlkey);
            WriteSqlToLog(querySql);
            IDbParameters parameters = BuildDbParameters(queryParameters);

            T queryResult = null;
            queryResult = AdoTemplate.QueryForObjectDelegate<T>(CommandType.Text, querySql, delegate(IDataReader reader, int num)
            {
                return new DataReaderBuilder<T>().Builde(reader);
            }, parameters);

            return queryResult;
        }

        public IList<T> QueryForList<T>(string sqlkey, QueryParameters queryParameters) where T : class
        {
            string querySql = GetStatementSql(sqlkey);
            WriteSqlToLog(querySql);
            IDbParameters parameters = BuildDbParameters(queryParameters);

            IList<T> queryResult = null;

            //Do'nt use cache
            if (null == queryCacheService)
            {
                queryResult = QueryForList<T>(querySql, parameters);
                return queryResult;
            }

            //Use cache
            if (queryCacheService.Contains(sqlkey, queryParameters))
            {
                queryResult = queryCacheService.Get<IList<T>>(sqlkey, queryParameters);
            }
            else
            {
                queryResult = QueryForList<T>(querySql, parameters);

                queryCacheService.Add<IList<T>>(sqlkey, queryParameters, queryResult);
            }
            return queryResult;
        }

        protected IList<T> QueryForList<T>(string querySql, IDbParameters parameters) where T : class
        {
            var builder = new DataReaderBuilder<T>();
            return AdoTemplate.QueryWithRowMapperDelegate<T>(CommandType.Text, querySql, delegate(IDataReader reader, int num)
            {
                return builder.Builde(reader);
            }, parameters);
        }

        protected IDbParameters BuildDbParameters(QueryParameters parameter)
        {
            if (null == parameter || parameter.Parameters.Count == 0)
            {
                return null;
            }

            IDbParametersBuilder builder = CreateDbParametersBuilder();
            foreach (QueryParameter entry in parameter.Parameters)
            {
                builder.Create().Name(entry.Name).Value(entry.Value).Type(entry.DbType);
                log.Info("=====>>SQL Parameters : " + entry);
            }

            return builder.GetParameters();
        }

        protected void WriteSqlToLog(string sql)
        {
            log.Info("=====>>SQL String : " + sql);
        }

        protected string GetStatementSql(string sqlkey)
        {
            StatementManagement statementManagement = SpringObjectUtil.Instance.StatementManagement;
            return statementManagement.SqlStatements.ContainsKey(sqlkey) ? statementManagement.SqlStatements[sqlkey] : sqlkey;
        }

        public Page<T> QueryForPage<T>(string queryKey, QueryParameters queryParameters, bool countStartFrom, long pageNumber, long pageSize) where T : class
        {
            string querySql = GetStatementSql(queryKey);
            string countSql = BuildCountSql(querySql, countStartFrom);
            return QueryForPage<T>(querySql, countSql, queryParameters, pageNumber, pageSize);
        }

        public Page<T> QueryForPage<T>(string queryKey, string countKey, QueryParameters queryParameters, long pageNumber, long pageSize) where T : class
        {
            if (String.IsNullOrWhiteSpace(queryKey))
            {
                throw new ArgumentNullException("querySql", "The SQL string is must not null or empty.");
            }
            string querySql = GetStatementSql(queryKey);
            string countSql = GetStatementSql(countKey);
            countSql = String.IsNullOrWhiteSpace(countSql) ? BuildCountSql(querySql, false) : countSql;
            long total = Convert.ToInt64(QueryForObject<object>(countSql, queryParameters));
            if (total == 0)
            {
                return new Page<T>();
            }

            string pageSql = BuildPageSql(querySql);
            long startNumber = GetStartNumber(pageNumber, pageSize, total);
            queryParameters.Add(QueryParameters.STR_FinishNumber, startNumber + pageSize, DbType.Int64)
                .Add(QueryParameters.STR_StartNumber, startNumber, DbType.Int64);
            IList<T> rowdata = QueryForList<T>(pageSql, queryParameters);
            return new Page<T>(pageNumber, total, pageSize, rowdata);
        }

        public Page<IDictionary<string, object>> QueryForPageMap(string queryKey, QueryParameters queryParameters, bool countStartFrom, long pageNumber, long pageSize)
        {
            string querySql = GetStatementSql(queryKey);
            string countSql = BuildCountSql(querySql, countStartFrom);
            return QueryForPageMap(querySql, countSql, queryParameters, pageNumber, pageSize);
        }

        public Page<IDictionary<string, object>> QueryForPageMap(string queryKey, string countKey, QueryParameters queryParameters, long pageNumber, long pageSize)
        {
            if (String.IsNullOrWhiteSpace(queryKey))
            {
                throw new ArgumentNullException("querySql", "The SQL string is must not null or empty.");
            }
            string querySql = GetStatementSql(queryKey);
            string countSql = GetStatementSql(countKey);
            countSql = String.IsNullOrWhiteSpace(countSql) ? BuildCountSql(querySql, false) : countSql;
            long total = Convert.ToInt64(QueryForObject<object>(countSql, queryParameters));
            if (total == 0)
            {
                return new Page<IDictionary<string, object>>();
            }

            string pageSql = BuildPageSql(querySql);
            long startNumber = GetStartNumber(pageNumber, pageSize, total);
            queryParameters.Add(QueryParameters.STR_FinishNumber, startNumber + pageSize, DbType.Int64)
                .Add(QueryParameters.STR_StartNumber, startNumber, DbType.Int64);
            IList<IDictionary<string, object>> rowdata = QueryForListMap(pageSql, queryParameters);
            return new Page<IDictionary<string, object>>(pageNumber, total, pageSize, rowdata);
        }

        protected long GetStartNumber(long pageNumber, long pageSize, long total)
        {
            long pageCount = (total / pageSize) + (total % pageSize == 0 ? 0 : 1);
            pageNumber = pageNumber < 1 ? 1 : (pageNumber > pageCount ? pageCount : pageNumber);
            return (pageNumber - 1) * pageSize + 1;
        }

        protected string BuildCountSql(string mainSql, bool startFrom)
        {
            if (String.IsNullOrWhiteSpace(mainSql))
            {
                return null;
            }
            StringBuilder sqlBuilder = new StringBuilder();
            if (startFrom)
            {
                sqlBuilder.Append("select count(1) ").Append(mainSql.Substring(mainSql.IndexOf("from")));
            }
            else
            {
                sqlBuilder.Append("select count(1) from (").Append(mainSql).Append(")");
            }
            return sqlBuilder.ToString();
        }

        protected string BuildPageSql(string mainSql)
        {
            return new StringBuilder("select * from (select rownum as sn,tab.* from (").Append(mainSql)
                .Append(") tab where rownum < :" + QueryParameters.STR_FinishNumber).Append(") where sn >= :" + QueryParameters.STR_StartNumber).ToString();
        }

        public object GetEntity(string ownerType, object ownerId)
        {
            if (String.IsNullOrWhiteSpace(ownerType) || null == ownerId)
            {
                return null;
            }

            Type entryType = CommonTools.GetModelType(ownerType);
            if (null == entryType)
            {
                return null;
            }

            Type hibernateType = SpringObjectUtil.Instance.HibernateTemplate.GetType();
            MethodInfo genericMethod = hibernateType.GetMethod("Get", new Type[] { typeof(object) }).MakeGenericMethod(entryType);
            object result = genericMethod.Invoke(SpringObjectUtil.Instance.HibernateTemplate, new object[] { ownerId });
            return result;
        }
    }
}
